/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/time.h>
#define TIME_BASE 1000
#define COUNT_FRAMES_NUM (300)

#include "hdi_drm_composition.h"
#include <cerrno>
#include "hdi_drm_layer.h"

namespace OHOS {
namespace HDI {
namespace DISPLAY {
HdiDrmComposition::HdiDrmComposition(std::shared_ptr<DrmConnector> &connector, std::shared_ptr<DrmCrtc> &crtc,
    std::shared_ptr<DrmDevice> &drmDevice)
    : mDrmDevice(drmDevice), mConnector(connector), mCrtc(crtc)
{
    DISPLAY_LOGD();
}

int32_t HdiDrmComposition::Init()
{
    DISPLAY_LOGD();
    mPrimPlanes.clear();
    mOverlayPlanes.clear();
    mPlanes.clear();
    DISPLAY_CHK_RETURN((mCrtc == nullptr), DISPLAY_FAILURE, DISPLAY_LOGE("crtc is null"));
    DISPLAY_CHK_RETURN((mConnector == nullptr), DISPLAY_FAILURE, DISPLAY_LOGE("connector is null"));
    DISPLAY_CHK_RETURN((mDrmDevice == nullptr), DISPLAY_FAILURE, DISPLAY_LOGE("drmDevice is null"));
    mPrimPlanes = mDrmDevice->GetDrmPlane(mCrtc->GetPipe(), DRM_PLANE_TYPE_PRIMARY);
    // mOverlayPlanes = mDrmDevice->GetDrmPlane(mCrtc->GetPipe(), DRM_PLANE_TYPE_OVERLAY);
    DISPLAY_CHK_RETURN((mPrimPlanes.size() == 0), DISPLAY_FAILURE, DISPLAY_LOGE("has no primary plane"));
    mPlanes.insert(mPlanes.end(), mPrimPlanes.begin(), mPrimPlanes.end());
    // mPlanes.insert(mPlanes.end(), mOverlayPlanes.begin(), mOverlayPlanes.end());
    return DISPLAY_SUCCESS;
}

int32_t HdiDrmComposition::SetLayers(std::vector<HdiLayer *> &layers, HdiLayer &clientLayer)
{
    // now we do not surpport present direct
    DISPLAY_LOGD();
    mCompLayers.clear();
    mClientLayer = &clientLayer;
    if(clientLayer.GetAcceleratorType() == ACCELERATOR_DPU)
    {
        mCompLayers.push_back(mClientLayer);
    }
    for (uint32_t i = 0; i < layers.size(); i++) {
        if(layers[i]->GetAcceleratorType() == ACCELERATOR_DPU)
        {
            mCompLayers.push_back(layers[i]);
        }
    }
    return DISPLAY_SUCCESS;
}

int32_t HdiDrmComposition::ApplyPlane(HdiDrmLayer &layer, DrmPlane &drmPlane, drmModeAtomicReqPtr pset)
{
#ifdef HIHOPE_OS_DEBUG
    HITRACE_METER(HITRACE_TAG_GRAPHIC_AGP);
#endif
    // set fence in
    int ret;
    int fenceFd = layer.GetAcquireFenceFd();
    int propId = drmPlane.GetPropFenceInId();
    DISPLAY_LOGD();
    if (propId != 0) {
        DISPLAY_LOGD("set the fence in prop");
        if (fenceFd >= 0) {
            ret = drmModeAtomicAddProperty(pset, drmPlane.GetId(), propId, fenceFd);
            DISPLAY_LOGD("set the IfenceProp plane id %{public}d, propId %{public}d, fenceFd %{public}d",
                drmPlane.GetId(), propId, fenceFd);
            DISPLAY_CHK_RETURN((ret < 0), DISPLAY_FAILURE, DISPLAY_LOGE("set IN_FENCE_FD failed"));
        }
    }

    // set fb id
    DrmGemBuffer *gemBuffer = layer.GetGemBuffer();
    DISPLAY_CHK_RETURN((gemBuffer == nullptr), DISPLAY_FAILURE, DISPLAY_LOGE("current gemBuffer is nullptr"));
    DISPLAY_CHK_RETURN((!gemBuffer->IsValid()), DISPLAY_FAILURE, DISPLAY_LOGE("the DrmGemBuffer is invalid"));
    ret = drmModeAtomicAddProperty(pset, drmPlane.GetId(), drmPlane.GetPropFbId(), gemBuffer->GetFbId());
    DISPLAY_LOGD("set the fb planeid %{public}d, propId %{public}d, fbId %{public}d", drmPlane.GetId(),
        drmPlane.GetPropFbId(), gemBuffer->GetFbId());
    DISPLAY_CHK_RETURN((ret < 0), DISPLAY_FAILURE, DISPLAY_LOGE("set fb id fialed errno : %{public}d", errno));

    // set crtc id
    ret = drmModeAtomicAddProperty(pset, drmPlane.GetId(), drmPlane.GetPropCrtcId(), mCrtc->GetId());
    DISPLAY_LOGD("set the crtc planeId %{public}d, propId %{public}d, crtcId %{public}d", drmPlane.GetId(),
        drmPlane.GetPropCrtcId(), mCrtc->GetId());
    DISPLAY_CHK_RETURN((ret < 0), DISPLAY_FAILURE, DISPLAY_LOGE("set crtc id fialed errno : %{public}d", errno));

    DrmMode mode;
    mConnector->GetModeFromId(mCrtc->GetActiveModeId(), mode);
    if(&layer == mClientLayer) {
        IRect tmpRect = {0, 0, mode.GetModeInfoPtr()->hdisplay, mode.GetModeInfoPtr()->vdisplay};
        layer.SetLayerCrop(&tmpRect);
        layer.SetLayerRegion(&tmpRect);
        static LayerAlpha hdiAlpha = {
            .enGlobalAlpha = 1,
            .enPixelAlpha = 0,
            .alpha0 = 255,
            .alpha1 = 255,
            .gAlpha = 255
        };
        layer.SetLayerAlpha(&hdiAlpha);
    }
    //fl0414++ set property_crtc_x ...
	IRect rect = layer.GetLayerDisplayRect();
    IRect crop = layer.GetLayerCrop();

    drmModeAtomicAddProperty(pset,  drmPlane.GetId(), drmPlane.property_crtc_x, rect.x);
    drmModeAtomicAddProperty(pset,  drmPlane.GetId(), drmPlane.property_crtc_y, rect.y);
    drmModeAtomicAddProperty(pset,  drmPlane.GetId(), drmPlane.property_crtc_w, rect.w);
    drmModeAtomicAddProperty(pset,  drmPlane.GetId(), drmPlane.property_crtc_h, rect.h);
    drmModeAtomicAddProperty(pset,  drmPlane.GetId(), drmPlane.property_src_x, crop.x << 16);
    drmModeAtomicAddProperty(pset,  drmPlane.GetId(), drmPlane.property_src_y, crop.y << 16);
    drmModeAtomicAddProperty(pset,  drmPlane.GetId(), drmPlane.property_src_w, crop.w << 16);
    drmModeAtomicAddProperty(pset,  drmPlane.GetId(), drmPlane.property_src_h, crop.h << 16);

    // DISPLAY_LOGE("crop={%{public}d,%{public}d:%{public}d,%{public}d}->disp={%{public}d,%{public}d:%{public}d,%{public}d}", 
    // crop.x, crop.y, crop.w, crop.h, rect.x, rect.y, rect.w, rect.h);
    
    switch (layer.GetTransFormType()) {
        case ROTATE_90: drmModeAtomicAddProperty(pset,  drmPlane.GetId(), drmPlane.property_rotation, DRM_MODE_ROTATE_270); break;
        case ROTATE_180: drmModeAtomicAddProperty(pset,  drmPlane.GetId(), drmPlane.property_rotation, DRM_MODE_ROTATE_180); break;
        case ROTATE_270: drmModeAtomicAddProperty(pset,  drmPlane.GetId(), drmPlane.property_rotation, DRM_MODE_ROTATE_90); break;
        default: drmModeAtomicAddProperty(pset,  drmPlane.GetId(), drmPlane.property_rotation, DRM_MODE_ROTATE_0); break;
    }
    drmModeAtomicAddProperty(pset,  drmPlane.GetId(), drmPlane.property_blend_mode, 0);
    if(layer.GetAlpha().enGlobalAlpha) {
        drmModeAtomicAddProperty(pset,  drmPlane.GetId(), drmPlane.property_alpha, layer.GetAlpha().gAlpha);
    }
    if((layer.GetCurrentBuffer()->GetFormat() >= PIXEL_FMT_YUV_422_I) && (layer.GetCurrentBuffer()->GetFormat() <= PIXEL_FMT_VYUY_422_PKG)) {
        drmModeAtomicAddProperty(pset,  drmPlane.GetId(), drmPlane.property_y2r_coef, 1);
    }

    return DISPLAY_SUCCESS;
}

int32_t HdiDrmComposition::UpdateMode(std::unique_ptr<DrmModeBlock> &modeBlock, drmModeAtomicReq &pset)
{
    // set the mode
    int ret;
    DISPLAY_LOGD();
    if (mCrtc->NeedModeSet()) {
        modeBlock = mConnector->GetModeBlockFromId(mCrtc->GetActiveModeId());
        if ((modeBlock != nullptr) && (modeBlock->GetBlockId() != DRM_INVALID_ID)) {
            // set to active
            DISPLAY_LOGD("set crtc to active id %{public}d ",  mCrtc->GetId());

            drmModeAtomicReq *req;
            req = drmModeAtomicAlloc();
            ret = drmModeAtomicAddProperty(req, mCrtc->GetId(), mCrtc->GetActivePropId(), 1);//(&pset, mCrtc->GetId(), mCrtc->GetActivePropId(), 1);
            DISPLAY_CHK_RETURN((ret < 0), DISPLAY_FAILURE,
                DISPLAY_LOGE("can not add the active prop errno %{public}d", errno));

            // set the mode id
            DISPLAY_LOGD("set the mode");
            ret = drmModeAtomicAddProperty(req, mCrtc->GetId(), mCrtc->GetModePropId(), modeBlock->GetBlockId());
            DISPLAY_LOGD("set the mode planeId %{public}d, propId %{public}d, GetBlockId: %{public}d", mCrtc->GetId(),
                mCrtc->GetModePropId(), modeBlock->GetBlockId());
            DISPLAY_CHK_RETURN((ret < 0), DISPLAY_FAILURE,
                DISPLAY_LOGE("can not add the mode prop errno %{public}d", errno));

            ret = drmModeAtomicAddProperty(req, mCrtc->GetId(), mCrtc->GetFpsChangePropId(), true);
            DISPLAY_LOGD("set frame change true, planeId %{public}d, propId %{public}d", mCrtc->GetId(),
                mCrtc->GetFpsChangePropId());
            DISPLAY_CHK_RETURN((ret < 0), DISPLAY_FAILURE,
                DISPLAY_LOGE("can not add the fps change prop errno %{public}d", errno));

            ret = drmModeAtomicAddProperty(req, mConnector->GetId(), mConnector->GetPropCrtcId(), mCrtc->GetId());
            DISPLAY_LOGD("set the connector id: %{public}d, propId %{public}d, crtcId %{public}d", mConnector->GetId(),
                mConnector->GetPropCrtcId(), mCrtc->GetId());
            DISPLAY_CHK_RETURN((ret < 0), DISPLAY_FAILURE,
                DISPLAY_LOGE("can not add the crtc id prop %{public}d", errno));

            int drmFd = mDrmDevice->GetDrmFd();
            drmModeAtomicCommit(drmFd, req, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
            drmModeAtomicFree(req);

            mCrtc->CleanNeedModeSet();
        }
    }
    return DISPLAY_SUCCESS;
}

int32_t HdiDrmComposition::Apply(bool modeSet)
{
    uint64_t crtcOutFence = -1;
    HdiDrmLayer *layer = nullptr;
    int ret;
    std::unique_ptr<DrmModeBlock> modeBlock;
    int drmFd = mDrmDevice->GetDrmFd();
    DISPLAY_LOGD();
    DISPLAY_CHK_RETURN((mPlanes.size() < mCompLayers.size()), DISPLAY_FAILURE, DISPLAY_LOGE("plane not enough"));

    if(frame_num%COUNT_FRAMES_NUM == 0){
        struct timeval tv;
        gettimeofday(&tv, NULL);
        frame_time_end = tv.tv_sec * TIME_BASE + tv.tv_usec / TIME_BASE;
        if(frame_time_start){
                DISPLAY_LOGE("Drm fps %{public}lu", COUNT_FRAMES_NUM*TIME_BASE/(frame_time_end-frame_time_start));
        }
        frame_time_start = frame_time_end;
    }
    frame_num++;

    drmModeAtomicReqPtr pset = drmModeAtomicAlloc();
    DISPLAY_CHK_RETURN((pset == nullptr), DISPLAY_NULL_PTR,
        DISPLAY_LOGE("drm atomic alloc failed errno %{public}d", errno));
    AtomicReqPtr atomicReqPtr = AtomicReqPtr(pset);

    // set the outFence property
    ret = drmModeAtomicAddProperty(atomicReqPtr.Get(), mCrtc->GetId(), mCrtc->GetOutFencePropId(),
        (uint64_t)&crtcOutFence);

    DISPLAY_LOGD("Apply Set OutFence crtc id: %{public}d, fencePropId %{public}d", mCrtc->GetId(),
        mCrtc->GetOutFencePropId());
    DISPLAY_CHK_RETURN((ret < 0), DISPLAY_FAILURE, DISPLAY_LOGE("set the outfence property of crtc failed "));

    // set the plane info.
    DISPLAY_LOGD("mCompLayers size %{public}zd", mCompLayers.size());
    for (uint32_t i = 0; i < mCompLayers.size(); i++) {
        layer = static_cast<HdiDrmLayer *>(mCompLayers[i]);
        auto &drmPlane = mPlanes[i];
        ret = ApplyPlane(*layer, *drmPlane, atomicReqPtr.Get());
        if (ret != DISPLAY_SUCCESS) {
            DISPLAY_LOGE("apply plane failed");
            break;
        }
    }
    ret = UpdateMode(modeBlock, *(atomicReqPtr.Get()));
    DISPLAY_CHK_RETURN((ret != DISPLAY_SUCCESS), DISPLAY_FAILURE, DISPLAY_LOGE("update mode failed"));
    uint32_t flags = DRM_MODE_ATOMIC_ALLOW_MODESET | DRM_MODE_ATOMIC_NONBLOCK;
    if(PowerOff){
        //do nothing
    }else{
        ret = drmModeAtomicCommit(drmFd, atomicReqPtr.Get(), flags, nullptr);
        DISPLAY_CHK_RETURN((ret != 0), DISPLAY_FAILURE,
        DISPLAY_LOGE("drmModeAtomicCommit failed %{public}d errno %{public}d", ret, errno));
    }

    // set the release fence
    for (uint32_t i = 0; i < mCompLayers.size(); i++) {
        layer = static_cast<HdiDrmLayer *>(mCompLayers[i]);
        if(i == 0) {
            layer->SetReleaseFence(crtcOutFence);
        }
        else {
            layer->SetReleaseFence(dup(crtcOutFence));
        }
    }

    if(mClientLayer->GetAcceleratorType() != ACCELERATOR_DPU) {
        mClientLayer->SetReleaseFence(dup(crtcOutFence));
    }
    return DISPLAY_SUCCESS;
}

} // OHOS
} // HDI
} // DISPLAY
